package types

import (
	"context"
	"fmt"
	"time"

	"gitee.com/thubcc/p2pgit/constant"
	"gopkg.in/src-d/go-git.v4/plumbing"
	"gopkg.in/src-d/go-git.v4/plumbing/format/packfile"
)

// Asyncer async operator
type Asyncer interface {
	GetAsync(context.Context, Selector, func(interface{}) error) context.CancelFunc
	Hit(Hash) bool
}

// SomeAsyncer some asyncer
type SomeAsyncer []Asyncer

// Hit inferace implement
func (as SomeAsyncer) Hit(h Hash) bool {
	for _, r := range as {
		if r.Hit(h) {
			return true
		}
	}
	return false
}

// GetAsync inferace implement
func (as SomeAsyncer) GetAsync(ctx context.Context, s Selector, cb func(interface{}) error) context.CancelFunc {
	sctx, cancel := context.WithDeadline(
		ctx,
		time.Now().Add(time.Second*constant.AsyncGetTimer),
	)
	for _, r := range as {
		go r.GetAsync(sctx, s, func(r interface{}) error {
			cancel()
			return cb(r)
		})
	}
	return cancel
}

// GetSync sync get
func (as SomeAsyncer) GetSync(s Selector) (r interface{}, err error) {
	ctx := context.TODO()
	as.GetAsync(ctx, s, func(res interface{}) error {
		switch rr := res.(type) {
		case []byte:
			r = res
		case error:
			err = rr
		default:
			err = fmt.Errorf("Bad get result")
		}
		return err
	})
	return
}

// GetRaw interface
func (as SomeAsyncer) GetRaw(s Selector) (body []byte, err error) {
	var r interface{}
	r, err = as.GetSync(s)
	if err != nil {
		return
	}
	var ok bool
	body, ok = r.([]byte)
	if !ok {
		err = fmt.Errorf("Bad Raw")
	}
	return
}

// Get interface
func (as SomeAsyncer) Get(s Selector) (body []byte, t plumbing.ObjectType, h32 *Header32, err error) {
	var r []byte
	r, err = as.GetRaw(s)
	if err != nil {
		return
	}
	return RawParse(r)
}

//ResolveSync resolve object sync
func (as SomeAsyncer) ResolveSync(s Selector) (body []byte, t plumbing.ObjectType, err error) {
	var gs SomeGeter
	gs = append(gs, as)
	return ResolveObj(gs, s)
}

//ResolveAsync resolve object sync
func (as SomeAsyncer) ResolveAsync(ctx context.Context, s Selector, cb func(Selector, []byte, plumbing.ObjectType, error) error) {
	as.GetAsync(ctx, s, func(res interface{}) error {
		raw, ok := res.([]byte)
		if !ok {
			err := fmt.Errorf("Bad Raw")
			err = cb(s, []byte{}, plumbing.InvalidObject, err)
			if err != nil {
				fmt.Println(err)
			}
			return err
		}
		if len(raw) == 0 {
			fmt.Printf("raw empty %v %s\n", res, s)
			return ErrorNoFind
		}
		body, t, bh, err := RawParse(raw)
		if err != nil {
			fmt.Println("no delta", err)
			return err
		}
		if bh == nil {
			err := cb(s, body, t, err)
			if err != nil {
				fmt.Println("no delta cb", err)
				return err
			}
			return nil
		}
		as.ResolveAsync(ctx, FullSelect(bh.ToHash256()), func(bs Selector, base []byte, t plumbing.ObjectType, err error) error {
			if err != nil {
				fmt.Println("delta ", err, bh.Hash.String(), bh.Size)
				return cb(nil, []byte{}, plumbing.InvalidObject, err)
			}

			body, err = packfile.PatchDelta(base, body)
			if err != nil {
				fmt.Println("patch", err)
			}
			return cb(s, body, t, err)
		})
		return nil
	})
	return
}
